// wglmesh.cpp
//

#include "config.h"
#include "wgl/wglmesh.hpp"
#include "wgl/wgl_gl.hpp"

#include "tinyobjloader/tiny_obj_loader.h"

#include "glm/gtc/matrix_transform.hpp"

using namespace wgl;



// Mesh::Mesh
//
Mesh::Mesh ()
{
}



// Mesh::axis
//
void Mesh::axis ()
{
  static constexpr glm::vec3 n{};
  static constexpr glm::vec2 t{};
  static constexpr Vertex axis_vertices[6] =
    {
      // X
      {{0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, n, t},
      {{1.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, n, t}, 
      // Y
      {{0.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, n, t}, 
      {{0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, n, t}, 
      // Z
      {{0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, n, t}, 
      {{0.f, 0.f, 1.f}, {0.f, 0.f, 1.f}, n, t},
    };

  polygon_type = GL_LINES;
  for (int i = 0; i < 6; i++)
    {
      vertices.emplace_back(axis_vertices[i]);
      indices.emplace_back(i);
    }
}



// Mesh::cube
//
void Mesh::cube ()
{
  static constexpr glm::vec3 col{1.f, 1.f, 1.f};
  static constexpr float n = 0.5f;
  static constexpr Vertex cube_vertices[8] =
    {
      { { -n,  n,  n }, col, {}, { 0.f, 0.f } },
      { {  n,  n,  n }, col, {}, { 1.f, 0.f } },
      { {  n, -n,  n }, col, {}, { 1.f, 1.f } },
      { { -n, -n,  n }, col, {}, { 0.f, 1.f } },
      { { -n,  n, -n }, col, {}, { 0.f, 0.f } },
      { {  n,  n, -n }, col, {}, { 1.f, 0.f } },
      { {  n, -n, -n }, col, {}, { 1.f, 1.f } },
      { { -n, -n, -n }, col, {}, { 0.f, 1.f } },
    };

  polygon_type = GL_TRIANGLES;
  
  vertices.clear();
  for (int i = 0; i < 8; ++i)
    vertices.emplace_back(cube_vertices[i]);

  indices.clear();
  indices =
    {
      0, 1, 3, 1, 2, 3,
      1, 5, 6, 1, 6, 2,
      4, 5, 7, 5, 7, 6,
      4, 0, 7, 0, 3, 7,
      4, 5, 0, 5, 1, 0,
      3, 2, 7, 2, 6, 7,
    };

  WGL_TRACE("cube: %lu vertices, %lu indices",
            WGL_LU(vertices.size()),
            WGL_LU(indices.size()));
}



// Mesh::load
//
void Mesh::load ( const std::string &filepath )
{
  // [FIXME]
  glm::vec3 color{1.f, 1.f, 1.f};
  
  std::filesystem::path path{filepath};
  path = std::filesystem::canonical(path);

  WGL_TRACE("loading model from '%s'", path.string().c_str());

  polygon_type = GL_TRIANGLES;
  
  tinyobj::ObjReaderConfig config{};
  config.mtl_search_path = path.parent_path().string();

  tinyobj::ObjReader reader{};
  if (!reader.ParseFromFile(path.string(), config))
    {
      std::string err = reader.Error();
      WGL_ERROR("could not load model %s: %s", path.string().c_str(), err.c_str());
    }
  if (!reader.Warning().empty())
    WGL_WARNING("tinyobjloader: %s", reader.Warning().c_str());

  const tinyobj::attrib_t &attrib = reader.GetAttrib();
  const std::vector<tinyobj::shape_t> &shapes = reader.GetShapes();
  // const std::vector<tinyobj::material_t> &materials = reader.GetMaterials();

  size_t start_size = vertices.size();
  
  // WGL_TRACE(" - %lu shapes", shapes.size());
  for (size_t s = 0; s < shapes.size(); s++)
    {
      size_t index_offset = 0;
      // WGL_TRACE("   - shape[%lu]: %lu faces", s, shapes[s].mesh.num_face_vertices.size());
      for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
        {
          size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]);
          // WGL_TRACE("     - face[%lu]: %lu vertices", f, fv);
          WGL_ASSERT(fv == 3); // [TODO]non-triangles
          for (size_t v = 0; v < fv; v++)
            {
              tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];

              // vertices
              float vx = float(attrib.vertices[3*size_t(idx.vertex_index)+0]);
              float vy = float(attrib.vertices[3*size_t(idx.vertex_index)+1]);
              float vz = float(attrib.vertices[3*size_t(idx.vertex_index)+2]);

              // normals
              float nx, ny, nz;
              if (idx.normal_index >= 0)
                {
                  nx = float(attrib.normals[3*size_t(idx.normal_index)+0]);
                  ny = float(attrib.normals[3*size_t(idx.normal_index)+1]);
                  nz = float(attrib.normals[3*size_t(idx.normal_index)+2]);
                }
              else
                {
                  WGL_TRACE("        - no normals!");
                  nx = ny = nz = 0.f;
                }

              // tex coords
              float tx, ty;
              if (idx.texcoord_index >= 0)
                {
                  tx = float(attrib.texcoords[2*size_t(idx.texcoord_index)+0]);
                  ty = float(attrib.texcoords[2*size_t(idx.texcoord_index)+1]);
                }
              else
                {
                  WGL_TRACE("        - no tex coords!");
                  tx = ty = 0.f;
                }
              // WGL_TRACE("        - V[%lu]: {%.1f,%.1f,%.1f} {%.1f,%.1f,%.1f} {%.1f,%.1f}",
              //           v, vx, vy, vz, nx, ny, nz, tx, ty);

              vertices.emplace_back(Vertex{{vx, vy, vz},
                                           color,
                                           glm::normalize(glm::vec3{nx, ny, nz}),
                                           {tx, ty}});
            }
          index_offset += fv;
        }
    }

  WGL_TRACE("%lu vertices loaded", WGL_LU(vertices.size() - start_size));

  // [TODO]
  indices.resize(vertices.size());
  for (size_t i = 0; i < vertices.size(); i++)
    indices[i] = i;
}



// Mesh::create_normals
//
void Mesh::create_normals ( wgl::Mesh &dest,
                            float len )
{
  glm::vec3 color{1.f, 1.f, 0.f};
  std::vector<Vertex> dest_vertices;
  std::vector<GLuint> dest_indices;

  GLuint index = 0;
  for (size_t i = 0; i < vertices.size(); ++i)
    {
      Vertex vtx1 = vertices[i];
      vtx1.col = color;

      Vertex vtx2 = vtx1;
      vtx2.pos += vtx1.norm * len;
      
      dest_vertices.push_back(vtx1);
      dest_vertices.push_back(vtx2);
      
      dest_indices.push_back(index);
      dest_indices.push_back(index+1);

      index += 2;
    }

  dest.swap(GL_LINES, dest_vertices, dest_indices);
}



// Mesh::swap
//
void Mesh::swap ( GLenum polygon_type,
                  std::vector<Vertex> &vertices,
                  std::vector<GLuint> &indices )
{
  this->polygon_type = polygon_type;
  this->vertices.swap(vertices);
  this->indices.swap(indices);
}



// Mesh::scale
//
void Mesh::scale ( float sx,
                   float sy,
                   float sz )
{
  for (auto &vtx: vertices)
    {
      vtx.pos.x *= sx;
      vtx.pos.y *= sy;
      vtx.pos.z *= sz;
    }
}



// Mesh::uv_scale
//
void Mesh::uv_scale ( float sx,
                      float sy )
{
  for (auto &vtx: vertices)
    {
      vtx.uv.x *= sx;
      vtx.uv.y *= sy;
    }
}
